#-*- coding: utf-8 -*-
import os
import sys
import socket
import signal
import fnmatch
import logging
import datetime
import subprocess
import pkg_resources
from time import sleep

from nose import util

from selenose.excepts import ToolError
from selenose.consts import GRID_CONFIG, LOGGER, WORK

# Get a logger
logger = logging.getLogger('%s.%s' % (LOGGER, __name__))

class Starter(object):
    '''Check file content to find a tag'''
    
    DELAY = 0.2
    
    def __init__(self, log):
        '''Initialize the starter'''
        # Store the log file descriptor
        self.log = log
        # Flush the log
        self.log.flush()
        # Get start position for log
        self.ilog = self.log.tell()
        
    def wait(self, tag, timeout):
        '''Wait till the tag or timeout'''
        # Get expiration delay
        expire = datetime.datetime.now() + datetime.timedelta(seconds=timeout)
        # Loop to find
        while True:
            # Check if expired
            if datetime.datetime.now() >= expire:
                # Expired
                raise ToolError('failed to find %s in %s' % (tag, self.log.name))
            # Check if in log
            if self.find(self.log, tag, self.ilog): return
            # Else wait
            else: sleep(Starter.DELAY)

    def find(self, fd, tag, start):
        '''Try to find the tag in the file'''
        # Flush
        fd.flush()
        # Get the content of the file
        fil = open(fd.name, 'r')
        # Go to the start position
        fil.seek(start)
        # Read
        content = fil.read()
        # Close
        fil.close()
        # Check if found
        return content.find(tag) != -1

def get_last_version(folders, pattern, default=None):
    '''Get the last version of a jar given its folder and pattern'''
    # List the versions
    versions = {}
    # Go threw the folders
    for folder in folders:
        # Go threw the folder
        for fn in os.listdir(folder):
            # Check if match
            if fnmatch.fnmatch(fn, pattern):
                # If yes, add to list
                versions[fn] = os.path.join(folder, fn)
    # Check if at least one file
    if len(versions) == 0:
        # Not found
        if default is None: raise ToolError('%s not found' % pattern)
        # Else get the entry
        else: return pkg_resources.resource_filename(*default)
    # Found at least one. Sort!
    else:
        # Get the keys
        keys = versions.keys()
        # Sort versions
        keys.sort(reverse=True)
        # Return full path
        return versions[keys[0]]
    
def get_available_port():
    '''Get an available port'''
    # Create a socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
    # Bin on 0 to get next one
    sock.bind(('', 0))
    # Listen
    sock.listen(1)
    # Get the port
    port = sock.getsockname()[1]
    # Close the socket
    sock.close()
    # Return the port
    return port

def kill(process):
    '''Kill a process'''
    # Check if defined
    if process is None: logger.info('process not started')
    # Check if is finished
    elif process.poll() is not None: logger.info('process already finished with status %d' % process.poll())
    # Else stop it
    elif sys.platform == 'win32':
        # Get the specific windows package
        import win32api
        # Close process
        win32api.TerminateProcess(win32api.OpenProcess(1, 0, process.pid), 0)
    # Classical kill
    else: os.kill(process.pid, signal.SIGTERM)

def java(exe, classpath, cls, log, *args):
    '''Execute Java process'''
    # Get the base line
    jargs = [ exe, '-classpath', classpath, cls, ]
    # Add arguments
    jargs.extend(args)
    # Log
    logger.debug('execute: %s' % (' '.join(jargs)))
    # Start and return process
    return subprocess.Popen(jargs, stdout=log, stderr=subprocess.STDOUT)

def django_admin(order, settings, path, log, err, *args):
    '''Execute Django administration process'''
    # Get environ
    environ = os.environ.copy()
    # Set settings
    environ['DJANGO_SETTINGS_MODULE'] = settings
    # No buffer on output
    environ['PYTHONUNBUFFERED'] = 'true'
    # Get the executable
    executable = os.path.abspath(sys.executable)
    # Create line
    dargs = [ executable, sys.platform == 'win32' and os.path.join(os.path.dirname(executable), 'Scripts', 'django-admin.py') or os.path.join(os.path.dirname(executable), 'django-admin.py'), ]
    # Add other data
    dargs.extend([ order, '--settings=%s' % settings, '--pythonpath=%s' % path, ])
    # Add arguments
    dargs.extend(args)
    # Check if error specified
    if err is None: err = subprocess.STDOUT
    # Log
    logger.debug('execute: %s' % (' '.join(dargs)))
    # Start and return the process
    return subprocess.Popen(dargs, env=environ, stdout=log, stderr=err, shell=False)

def create(folder):
    '''Create a folder'''
    # If not exists, create the folders
    if not os.path.exists(folder):
        # Create folder
        os.makedirs(folder)
        # Show that created
        return True
    # If exists and is a file, raise
    elif os.path.isfile(folder): raise ToolError('following path exists but is not a folder: %s' % folder)
    # Show that already exists
    return False

def geturlpath():
    '''Get the path where the URL of selenose server is stored'''
    # A file .selenose in user's HOME
    return os.path.join(os.getenv('USERPROFILE') or os.getenv('HOME'), '.selenose')

def geturl():
    '''Get the URL of the selenose server'''
    # Get the path
    path = geturlpath()
    # Check if exists
    if not os.path.isfile(path): return None
    # Get the file descriptor
    fd = open(path, 'r')
    # Read the content of the file
    url = fd.read()
    # Close the file descriptor
    fd.close()
    # Return the URL
    return url

def storeurl(url):
    '''Store the URL of selenose server'''
    # Get the file descriptor
    fd = open(geturlpath(), 'w')
    # Set the URL in the file
    fd.write(url)
    # Close the file descriptor
    fd.close()

def set_options(parser, binary=False):
    '''Add options to parser'''
    # Add grid configuration file
    parser.add_option('--grid',
                      action='store',
                      default=GRID_CONFIG,
                      dest='grid',
                      help='Grid configuration file (default: %s).' % GRID_CONFIG)
    # Add library folder
    parser.add_option('--lib',
                      action='append',
                      default=[],
                      dest='libs',
                      help='Java library folders. If not specified, extract needed libraries.')
    # Add Java executable
    parser.add_option('--java',
                      action='store',
                      default='java',
                      dest='java',
                      help='Java executable (default: java).')
    # Add settings file
    parser.add_option('--settings',
                      action='store',
                      default='settings',
                      dest='settings',
                      help='Django settings.')
    # Work folder
    parser.add_option('--work',
                      action='store',
                      default=WORK,
                      dest='work',
                      help='Selenose work folder (default: %s).' % WORK)
    # No server
    parser.add_option('--keep',
                      action='store_true',
                      default=False,
                      dest='keep',
                      help='Keep logs in work folder after tests.')
    # Exclude models from dump
    parser.add_option('--dumpdata-exclude',
                      action='append',
                      default=[],
                      dest='dumpdata_excludes',
                      metavar='appname|appname.ModelName',
                      help='Selenose creates a database dump after the first synchronization to be able to restore the initial database state before each test. In some very particular cases, some models can make this dump fail (with `djcelery` notably). Use this option to ignore these models.')
    # Check if start server
    if not binary:
        # No server
        parser.add_option('--no-servers',
                          action='store_true',
                          default=False,
                          dest='dev',
                          help='Do not start servers if already started with `selenose` binary.')

def gettell(path):
    '''Get the last position in the file'''
    # Check if exists
    if not os.path.isfile(path):
        # Raise an error
        raise ToolError("file doesn't exist: %s" % path)
    # Get the file descriptor
    fd = open(path, 'r')
    # Go to the end of the file
    fd.seek(0, 2)
    # Get the position
    tell = fd.tell()
    # Close the file descriptor
    fd.close()
    # Return the position
    return tell

def getcontent(path, start=0):
    '''Get the content of the file located at the provided path'''
    # Check if exists
    if not os.path.isfile(path):
        # Raise an error
        raise ToolError("file doesn't exist: %s" % path)
    # Get the file descriptor
    fd = open(path, 'r')
    # Go to the position
    fd.seek(start)
    # Get the content
    content = fd.read()
    # Close the file descriptor
    fd.close()
    # Return the content
    return content

def is_26_plus():
    '''Check if PYTHON version is 2.6 plus'''
    # Check versions
    return ( sys.version_info[0] > 2 ) or ( sys.version_info[0] == 2 and sys.version_info[1] >= 6 )

def to_list(option):
    '''
    Convert an `append` option to a list: handle <option> = a, b in configuration file and --<option> a --<option> b on command line.
    '''
    # Final options
    result = []
    # Go threw the options
    for elmt in util.tolist(option):
        # Call again
        result.extend(util.tolist(elmt))
    # Return result
    return result
        
    
